home *** CD-ROM | disk | FTP | other *** search
/ Komputer for Alle 2004 #2 / K-CD-2-2004.ISO / OpenOffice Sv / f_0397 / python-core-2.2.2 / lib / zipfile.py < prev   
Encoding:
Python Source  |  2003-07-18  |  24.0 KB  |  589 lines

  1. "Read and write ZIP files."
  2. # Written by James C. Ahlstrom jim@interet.com
  3. # All rights transferred to CNRI pursuant to the Python contribution agreement
  4.  
  5. import struct, os, time
  6. import binascii
  7.  
  8. try:
  9.     import zlib # We may need its compression method
  10. except ImportError:
  11.     zlib = None
  12.  
  13. __all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile",
  14.            "ZipInfo", "ZipFile", "PyZipFile"]
  15.  
  16. class BadZipfile(Exception):
  17.     pass
  18. error = BadZipfile      # The exception raised by this module
  19.  
  20. # constants for Zip file compression methods
  21. ZIP_STORED = 0
  22. ZIP_DEFLATED = 8
  23. # Other ZIP compression methods not supported
  24.  
  25. # Here are some struct module formats for reading headers
  26. structEndArchive = "<4s4H2lH"     # 9 items, end of archive, 22 bytes
  27. stringEndArchive = "PK\005\006"   # magic number for end of archive record
  28. structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
  29. stringCentralDir = "PK\001\002"   # magic number for central directory
  30. structFileHeader = "<4s2B4H3l2H"  # 12 items, file header record, 30 bytes
  31. stringFileHeader = "PK\003\004"   # magic number for file header
  32.  
  33. # indexes of entries in the central directory structure
  34. _CD_SIGNATURE = 0
  35. _CD_CREATE_VERSION = 1
  36. _CD_CREATE_SYSTEM = 2
  37. _CD_EXTRACT_VERSION = 3
  38. _CD_EXTRACT_SYSTEM = 4                  # is this meaningful?
  39. _CD_FLAG_BITS = 5
  40. _CD_COMPRESS_TYPE = 6
  41. _CD_TIME = 7
  42. _CD_DATE = 8
  43. _CD_CRC = 9
  44. _CD_COMPRESSED_SIZE = 10
  45. _CD_UNCOMPRESSED_SIZE = 11
  46. _CD_FILENAME_LENGTH = 12
  47. _CD_EXTRA_FIELD_LENGTH = 13
  48. _CD_COMMENT_LENGTH = 14
  49. _CD_DISK_NUMBER_START = 15
  50. _CD_INTERNAL_FILE_ATTRIBUTES = 16
  51. _CD_EXTERNAL_FILE_ATTRIBUTES = 17
  52. _CD_LOCAL_HEADER_OFFSET = 18
  53.  
  54. # indexes of entries in the local file header structure
  55. _FH_SIGNATURE = 0
  56. _FH_EXTRACT_VERSION = 1
  57. _FH_EXTRACT_SYSTEM = 2                  # is this meaningful?
  58. _FH_GENERAL_PURPOSE_FLAG_BITS = 3
  59. _FH_COMPRESSION_METHOD = 4
  60. _FH_LAST_MOD_TIME = 5
  61. _FH_LAST_MOD_DATE = 6
  62. _FH_CRC = 7
  63. _FH_COMPRESSED_SIZE = 8
  64. _FH_UNCOMPRESSED_SIZE = 9
  65. _FH_FILENAME_LENGTH = 10
  66. _FH_EXTRA_FIELD_LENGTH = 11
  67.  
  68. # Used to compare file passed to ZipFile
  69. import types
  70. _STRING_TYPES = (types.StringType,)
  71. if hasattr(types, "UnicodeType"):
  72.     _STRING_TYPES = _STRING_TYPES + (types.UnicodeType,)
  73.  
  74.  
  75. def is_zipfile(filename):
  76.     """Quickly see if file is a ZIP file by checking the magic number.
  77.  
  78.     Will not accept a ZIP archive with an ending comment.
  79.     """
  80.     try:
  81.         fpin = open(filename, "rb")
  82.         fpin.seek(-22, 2)               # Seek to end-of-file record
  83.         endrec = fpin.read()
  84.         fpin.close()
  85.         if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
  86.             return 1    # file has correct magic number
  87.     except IOError:
  88.         pass
  89.  
  90.  
  91. class ZipInfo:
  92.     """Class with attributes describing each file in the ZIP archive."""
  93.  
  94.     def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
  95.         self.filename = _normpath(filename) # Name of the file in the archive
  96.         self.date_time = date_time      # year, month, day, hour, min, sec
  97.         # Standard values:
  98.         self.compress_type = ZIP_STORED # Type of compression for the file
  99.         self.comment = ""               # Comment for each file
  100.         self.extra = ""                 # ZIP extra data
  101.         self.create_system = 0          # System which created ZIP archive
  102.         self.create_version = 20        # Version which created ZIP archive
  103.         self.extract_version = 20       # Version needed to extract archive
  104.         self.reserved = 0               # Must be zero
  105.         self.flag_bits = 0              # ZIP flag bits
  106.         self.volume = 0                 # Volume number of file header
  107.         self.internal_attr = 0          # Internal attributes
  108.         self.external_attr = 0          # External file attributes
  109.         # Other attributes are set by class ZipFile:
  110.         # header_offset         Byte offset to the file header
  111.         # file_offset           Byte offset to the start of the file data
  112.         # CRC                   CRC-32 of the uncompressed file
  113.         # compress_size         Size of the compressed file
  114.         # file_size             Size of the uncompressed file
  115.  
  116.     def FileHeader(self):
  117.         """Return the per-file header as a string."""
  118.         dt = self.date_time
  119.         dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
  120.         dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
  121.         if self.flag_bits & 0x08:
  122.             # Set these to zero because we write them after the file data
  123.             CRC = compress_size = file_size = 0
  124.         else:
  125.             CRC = self.CRC
  126.             compress_size = self.compress_size
  127.             file_size = self.file_size
  128.         header = struct.pack(structFileHeader, stringFileHeader,
  129.                  self.extract_version, self.reserved, self.flag_bits,
  130.                  self.compress_type, dostime, dosdate, CRC,
  131.                  compress_size, file_size,
  132.                  len(self.filename), len(self.extra))
  133.         return header + self.filename + self.extra
  134.  
  135.  
  136. # This is used to ensure paths in generated ZIP files always use
  137. # forward slashes as the directory separator, as required by the
  138. # ZIP format specification.
  139. if os.sep != "/":
  140.     def _normpath(path):
  141.         return path.replace(os.sep, "/")
  142. else:
  143.     def _normpath(path):
  144.         return path
  145.  
  146.  
  147. class ZipFile:
  148.     """ Class with methods to open, read, write, close, list zip files.
  149.  
  150.     z = ZipFile(file, mode="r", compression=ZIP_STORED)
  151.  
  152.     file: Either the path to the file, or a file-like object.
  153.           If it is a path, the file will be opened and closed by ZipFile.
  154.     mode: The mode can be either read "r", write "w" or append "a".
  155.     compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib).
  156.     """
  157.  
  158.     fp = None                   # Set here since __del__ checks it
  159.  
  160.     def __init__(self, file, mode="r", compression=ZIP_STORED):
  161.         """Open the ZIP file with mode read "r", write "w" or append "a"."""
  162.         if compression == ZIP_STORED:
  163.             pass
  164.         elif compression == ZIP_DEFLATED:
  165.             if not zlib:
  166.                 raise RuntimeError,\
  167.                       "Compression requires the (missing) zlib module"
  168.         else:
  169.             raise RuntimeError, "That compression method is not supported"
  170.         self.debug = 0  # Level of printing: 0 through 3
  171.         self.NameToInfo = {}    # Find file info given name
  172.         self.filelist = []      # List of ZipInfo instances for archive
  173.         self.compression = compression  # Method of compression
  174.         self.mode = key = mode[0]
  175.  
  176.         # Check if we were passed a file-like object
  177.         if type(file) in _STRING_TYPES:
  178.             self._filePassed = 0
  179.             self.filename = file
  180.             modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
  181.             self.fp = open(file, modeDict[mode])
  182.         else:
  183.             self._filePassed = 1
  184.             self.fp = file
  185.             self.filename = getattr(file, 'name', None)
  186.  
  187.         if key == 'r':
  188.             self._GetContents()
  189.         elif key == 'w':
  190.             pass
  191.         elif key == 'a':
  192.             fp = self.fp
  193.             fp.seek(-22, 2)             # Seek to end-of-file record
  194.             endrec = fp.read()
  195.             if endrec[0:4] == stringEndArchive and \
  196.                        endrec[-2:] == "\000\000":
  197.                 self._GetContents()     # file is a zip file
  198.                 # seek to start of directory and overwrite
  199.                 fp.seek(self.start_dir, 0)
  200.             else:               # file is not a zip file, just append
  201.                 fp.seek(0, 2)
  202.         else:
  203.             if not self._filePassed:
  204.                 self.fp.close()
  205.                 self.fp = None
  206.             raise RuntimeError, 'Mode must be "r", "w" or "a"'
  207.  
  208.     def _GetContents(self):
  209.         """Read the directory, making sure we close the file if the format
  210.         is bad."""
  211.         try:
  212.             self._RealGetContents()
  213.         except BadZipfile:
  214.             if not self._filePassed:
  215.                 self.fp.close()
  216.                 self.fp = None
  217.             raise
  218.  
  219.     def _RealGetContents(self):
  220.         """Read in the table of contents for the ZIP file."""
  221.         fp = self.fp
  222.         fp.seek(-22, 2)         # Start of end-of-archive record
  223.         filesize = fp.tell() + 22       # Get file size
  224.         endrec = fp.read(22)    # Archive must not end with a comment!
  225.         if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
  226.             raise BadZipfile, "File is not a zip file, or ends with a comment"
  227.         endrec = struct.unpack(structEndArchive, endrec)
  228.         if self.debug > 1:
  229.             print endrec
  230.         size_cd = endrec[5]             # bytes in central directory
  231.         offset_cd = endrec[6]   # offset of central directory
  232.         x = filesize - 22 - size_cd
  233.         # "concat" is zero, unless zip was concatenated to another file
  234.         concat = x - offset_cd
  235.         if self.debug > 2:
  236.             print "given, inferred, offset", offset_cd, x, concat
  237.         # self.start_dir:  Position of start of central directory
  238.         self.start_dir = offset_cd + concat
  239.         fp.seek(self.start_dir, 0)
  240.         total = 0
  241.         while total < size_cd:
  242.             centdir = fp.read(46)
  243.             total = total + 46
  244.             if centdir[0:4] != stringCentralDir:
  245.                 raise BadZipfile, "Bad magic number for central directory"
  246.             centdir = struct.unpack(structCentralDir, centdir)
  247.             if self.debug > 2:
  248.                 print centdir
  249.             filename = fp.read(centdir[_CD_FILENAME_LENGTH])
  250.             # Create ZipInfo instance to store file information
  251.             x = ZipInfo(filename)
  252.             x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
  253.             x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
  254.             total = (total + centdir[_CD_FILENAME_LENGTH]
  255.                      + centdir[_CD_EXTRA_FIELD_LENGTH]
  256.                      + centdir[_CD_COMMENT_LENGTH])
  257.             x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] + concat
  258.             # file_offset must be computed below...
  259.             (x.create_version, x.create_system, x.extract_version, x.reserved,
  260.                 x.flag_bits, x.compress_type, t, d,
  261.                 x.CRC, x.compress_size, x.file_size) = centdir[1:12]
  262.             x.volume, x.internal_attr, x.external_attr = centdir[15:18]
  263.             # Convert date/time code to (year, month, day, hour, min, sec)
  264.             x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
  265.                                      t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
  266.             self.filelist.append(x)
  267.             self.NameToInfo[x.filename] = x
  268.             if self.debug > 2:
  269.                 print "total", total
  270.         for data in self.filelist:
  271.             fp.seek(data.header_offset, 0)
  272.             fheader = fp.read(30)
  273.             if fheader[0:4] != stringFileHeader:
  274.                 raise BadZipfile, "Bad magic number for file header"
  275.             fheader = struct.unpack(structFileHeader, fheader)
  276.             # file_offset is computed here, since the extra field for
  277.             # the central directory and for the local file header
  278.             # refer to different fields, and they can have different
  279.             # lengths
  280.             data.file_offset = (data.header_offset + 30
  281.                                 + fheader[_FH_FILENAME_LENGTH]
  282.                                 + fheader[_FH_EXTRA_FIELD_LENGTH])
  283.             fname = fp.read(fheader[_FH_FILENAME_LENGTH])
  284.             if fname != data.filename:
  285.                 raise RuntimeError, \
  286.                       'File name in directory "%s" and header "%s" differ.' % (
  287.                           data.filename, fname)
  288.  
  289.     def namelist(self):
  290.         """Return a list of file names in the archive."""
  291.         l = []
  292.         for data in self.filelist:
  293.             l.append(data.filename)
  294.         return l
  295.  
  296.     def infolist(self):
  297.         """Return a list of class ZipInfo instances for files in the
  298.         archive."""
  299.         return self.filelist
  300.  
  301.     def printdir(self):
  302.         """Print a table of contents for the zip file."""
  303.         print "%-46s %19s %12s" % ("File Name", "Modified    ", "Size")
  304.         for zinfo in self.filelist:
  305.             date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
  306.             print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
  307.  
  308.     def testzip(self):
  309.         """Read all the files and check the CRC."""
  310.         for zinfo in self.filelist:
  311.             try:
  312.                 self.read(zinfo.filename)       # Check CRC-32
  313.             except:
  314.                 return zinfo.filename
  315.  
  316.     def getinfo(self, name):
  317.         """Return the instance of ZipInfo given 'name'."""
  318.         return self.NameToInfo[name]
  319.  
  320.     def read(self, name):
  321.         """Return file bytes (as a string) for name."""
  322.         if self.mode not in ("r", "a"):
  323.             raise RuntimeError, 'read() requires mode "r" or "a"'
  324.         if not self.fp:
  325.             raise RuntimeError, \
  326.                   "Attempt to read ZIP archive that was already closed"
  327.         zinfo = self.getinfo(name)
  328.         filepos = self.fp.tell()
  329.         self.fp.seek(zinfo.file_offset, 0)
  330.         bytes = self.fp.read(zinfo.compress_size)
  331.         self.fp.seek(filepos, 0)
  332.         if zinfo.compress_type == ZIP_STORED:
  333.             pass
  334.         elif zinfo.compress_type == ZIP_DEFLATED:
  335.             if not zlib:
  336.                 raise RuntimeError, \
  337.                       "De-compression requires the (missing) zlib module"
  338.             # zlib compress/decompress code by Jeremy Hylton of CNRI
  339.             dc = zlib.decompressobj(-15)
  340.             bytes = dc.decompress(bytes)
  341.             # need to feed in unused pad byte so that zlib won't choke
  342.             ex = dc.decompress('Z') + dc.flush()
  343.             if ex:
  344.                 bytes = bytes + ex
  345.         else:
  346.             raise BadZipfile, \
  347.                   "Unsupported compression method %d for file %s" % \
  348.             (zinfo.compress_type, name)
  349.         crc = binascii.crc32(bytes)
  350.         if crc != zinfo.CRC:
  351.             raise BadZipfile, "Bad CRC-32 for file %s" % name
  352.         return bytes
  353.  
  354.     def _writecheck(self, zinfo):
  355.         """Check for errors before writing a file to the archive."""
  356.         if self.NameToInfo.has_key(zinfo.filename):
  357.             if self.debug:      # Warning for duplicate names
  358.                 print "Duplicate name:", zinfo.filename
  359.         if self.mode not in ("w", "a"):
  360.             raise RuntimeError, 'write() requires mode "w" or "a"'
  361.         if not self.fp:
  362.             raise RuntimeError, \
  363.                   "Attempt to write ZIP archive that was already closed"
  364.         if zinfo.compress_type == ZIP_DEFLATED and not zlib:
  365.             raise RuntimeError, \
  366.                   "Compression requires the (missing) zlib module"
  367.         if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
  368.             raise RuntimeError, \
  369.                   "That compression method is not supported"
  370.  
  371.     def write(self, filename, arcname=None, compress_type=None):
  372.         """Put the bytes from filename into the archive under the name
  373.         arcname."""
  374.         st = os.stat(filename)
  375.         mtime = time.localtime(st[8])
  376.         date_time = mtime[0:6]
  377.         # Create ZipInfo instance to store file information
  378.         if arcname is None:
  379.             zinfo = ZipInfo(filename, date_time)
  380.         else:
  381.             zinfo = ZipInfo(arcname, date_time)
  382.         zinfo.external_attr = st[0] << 16       # Unix attributes
  383.         if compress_type is None:
  384.             zinfo.compress_type = self.compression
  385.         else:
  386.             zinfo.compress_type = compress_type
  387.         self._writecheck(zinfo)
  388.         fp = open(filename, "rb")
  389.         zinfo.flag_bits = 0x00
  390.         zinfo.header_offset = self.fp.tell()    # Start of header bytes
  391.         # Must overwrite CRC and sizes with correct data later
  392.         zinfo.CRC = CRC = 0
  393.         zinfo.compress_size = compress_size = 0
  394.         zinfo.file_size = file_size = 0
  395.         self.fp.write(zinfo.FileHeader())
  396.         zinfo.file_offset = self.fp.tell()      # Start of file bytes
  397.         if zinfo.compress_type == ZIP_DEFLATED:
  398.             cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  399.                  zlib.DEFLATED, -15)
  400.         else:
  401.             cmpr = None
  402.         while 1:
  403.             buf = fp.read(1024 * 8)
  404.             if not buf:
  405.                 break
  406.             file_size = file_size + len(buf)
  407.             CRC = binascii.crc32(buf, CRC)
  408.             if cmpr:
  409.                 buf = cmpr.compress(buf)
  410.                 compress_size = compress_size + len(buf)
  411.             self.fp.write(buf)
  412.         fp.close()
  413.         if cmpr:
  414.             buf = cmpr.flush()
  415.             compress_size = compress_size + len(buf)
  416.             self.fp.write(buf)
  417.             zinfo.compress_size = compress_size
  418.         else:
  419.             zinfo.compress_size = file_size
  420.         zinfo.CRC = CRC
  421.         zinfo.file_size = file_size
  422.         # Seek backwards and write CRC and file sizes
  423.         position = self.fp.tell()       # Preserve current position in file
  424.         self.fp.seek(zinfo.header_offset + 14, 0)
  425.         self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
  426.               zinfo.file_size))
  427.         self.fp.seek(position, 0)
  428.         self.filelist.append(zinfo)
  429.         self.NameToInfo[zinfo.filename] = zinfo
  430.  
  431.     def writestr(self, zinfo, bytes):
  432.         """Write a file into the archive.  The contents is the string
  433.         'bytes'."""
  434.         self._writecheck(zinfo)
  435.         zinfo.file_size = len(bytes)            # Uncompressed size
  436.         zinfo.CRC = binascii.crc32(bytes)       # CRC-32 checksum
  437.         if zinfo.compress_type == ZIP_DEFLATED:
  438.             co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
  439.                  zlib.DEFLATED, -15)
  440.             bytes = co.compress(bytes) + co.flush()
  441.             zinfo.compress_size = len(bytes)    # Compressed size
  442.         else:
  443.             zinfo.compress_size = zinfo.file_size
  444.         zinfo.header_offset = self.fp.tell()    # Start of header bytes
  445.         self.fp.write(zinfo.FileHeader())
  446.         zinfo.file_offset = self.fp.tell()      # Start of file bytes
  447.         self.fp.write(bytes)
  448.         if zinfo.flag_bits & 0x08:
  449.             # Write CRC and file sizes after the file data
  450.             self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
  451.                   zinfo.file_size))
  452.         self.filelist.append(zinfo)
  453.         self.NameToInfo[zinfo.filename] = zinfo
  454.  
  455.     def __del__(self):
  456.         """Call the "close()" method in case the user forgot."""
  457.         self.close()
  458.  
  459.     def close(self):
  460.         """Close the file, and for mode "w" and "a" write the ending
  461.         records."""
  462.         if self.fp is None:
  463.             return
  464.         if self.mode in ("w", "a"):             # write ending records
  465.             count = 0
  466.             pos1 = self.fp.tell()
  467.             for zinfo in self.filelist:         # write central directory
  468.                 count = count + 1
  469.                 dt = zinfo.date_time
  470.                 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
  471.                 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
  472.                 centdir = struct.pack(structCentralDir,
  473.                   stringCentralDir, zinfo.create_version,
  474.                   zinfo.create_system, zinfo.extract_version, zinfo.reserved,
  475.                   zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
  476.                   zinfo.CRC, zinfo.compress_size, zinfo.file_size,
  477.                   len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
  478.                   0, zinfo.internal_attr, zinfo.external_attr,
  479.                   zinfo.header_offset)
  480.                 self.fp.write(centdir)
  481.                 self.fp.write(zinfo.filename)
  482.                 self.fp.write(zinfo.extra)
  483.                 self.fp.write(zinfo.comment)
  484.             pos2 = self.fp.tell()
  485.             # Write end-of-zip-archive record
  486.             endrec = struct.pack(structEndArchive, stringEndArchive,
  487.                      0, 0, count, count, pos2 - pos1, pos1, 0)
  488.             self.fp.write(endrec)
  489.             self.fp.flush()
  490.         if not self._filePassed:
  491.             self.fp.close()
  492.         self.fp = None
  493.  
  494.  
  495. class PyZipFile(ZipFile):
  496.     """Class to create ZIP archives with Python library files and packages."""
  497.  
  498.     def writepy(self, pathname, basename = ""):
  499.         """Add all files from "pathname" to the ZIP archive.
  500.  
  501.         If pathname is a package directory, search the directory and
  502.         all package subdirectories recursively for all *.py and enter
  503.         the modules into the archive.  If pathname is a plain
  504.         directory, listdir *.py and enter all modules.  Else, pathname
  505.         must be a Python *.py file and the module will be put into the
  506.         archive.  Added modules are always module.pyo or module.pyc.
  507.         This method will compile the module.py into module.pyc if
  508.         necessary.
  509.         """
  510.         dir, name = os.path.split(pathname)
  511.         if os.path.isdir(pathname):
  512.             initname = os.path.join(pathname, "__init__.py")
  513.             if os.path.isfile(initname):
  514.                 # This is a package directory, add it
  515.                 if basename:
  516.                     basename = "%s/%s" % (basename, name)
  517.                 else:
  518.                     basename = name
  519.                 if self.debug:
  520.                     print "Adding package in", pathname, "as", basename
  521.                 fname, arcname = self._get_codename(initname[0:-3], basename)
  522.                 if self.debug:
  523.                     print "Adding", arcname
  524.                 self.write(fname, arcname)
  525.                 dirlist = os.listdir(pathname)
  526.                 dirlist.remove("__init__.py")
  527.                 # Add all *.py files and package subdirectories
  528.                 for filename in dirlist:
  529.                     path = os.path.join(pathname, filename)
  530.                     root, ext = os.path.splitext(filename)
  531.                     if os.path.isdir(path):
  532.                         if os.path.isfile(os.path.join(path, "__init__.py")):
  533.                             # This is a package directory, add it
  534.                             self.writepy(path, basename)  # Recursive call
  535.                     elif ext == ".py":
  536.                         fname, arcname = self._get_codename(path[0:-3],
  537.                                          basename)
  538.                         if self.debug:
  539.                             print "Adding", arcname
  540.                         self.write(fname, arcname)
  541.             else:
  542.                 # This is NOT a package directory, add its files at top level
  543.                 if self.debug:
  544.                     print "Adding files from directory", pathname
  545.                 for filename in os.listdir(pathname):
  546.                     path = os.path.join(pathname, filename)
  547.                     root, ext = os.path.splitext(filename)
  548.                     if ext == ".py":
  549.                         fname, arcname = self._get_codename(path[0:-3],
  550.                                          basename)
  551.                         if self.debug:
  552.                             print "Adding", arcname
  553.                         self.write(fname, arcname)
  554.         else:
  555.             if pathname[-3:] != ".py":
  556.                 raise RuntimeError, \
  557.                       'Files added with writepy() must end with ".py"'
  558.             fname, arcname = self._get_codename(pathname[0:-3], basename)
  559.             if self.debug:
  560.                 print "Adding file", arcname
  561.             self.write(fname, arcname)
  562.  
  563.     def _get_codename(self, pathname, basename):
  564.         """Return (filename, archivename) for the path.
  565.  
  566.         Given a module name path, return the correct file path and
  567.         archive name, compiling if necessary.  For example, given
  568.         /python/lib/string, return (/python/lib/string.pyc, string).
  569.         """
  570.         file_py  = pathname + ".py"
  571.         file_pyc = pathname + ".pyc"
  572.         file_pyo = pathname + ".pyo"
  573.         if os.path.isfile(file_pyo) and \
  574.                             os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
  575.             fname = file_pyo    # Use .pyo file
  576.         elif not os.path.isfile(file_pyc) or \
  577.              os.stat(file_pyc)[8] < os.stat(file_py)[8]:
  578.             import py_compile
  579.             if self.debug:
  580.                 print "Compiling", file_py
  581.             py_compile.compile(file_py, file_pyc)
  582.             fname = file_pyc
  583.         else:
  584.             fname = file_pyc
  585.         archivename = os.path.split(fname)[1]
  586.         if basename:
  587.             archivename = "%s/%s" % (basename, archivename)
  588.         return (fname, archivename)
  589.